﻿using CKS.AGV.Core.Enum;
using CKS.AGV.Core.PlcActions;
using CKS.AGV.Core.PlcActions.OP10;
using CKS.AGV.Core.PlcActions.OP20;
using CKS.Core.Framework;
using CKS.Core.Framework.Models;
using CKS.Models;
using CKS.PLC.Core;
using Microsoft.Extensions.DependencyInjection;
using S7.Net;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;



namespace CKS.AGV.Core
{
    /// <summary>
    /// plc客户端
    /// 主要功能，读取订阅点信息，调用处理类进行订阅点的处理
    /// </summary>
    public class PlcClient
    {
        PlcConfig plcConfig;
        IServiceProvider serviceProvider;
        ILogger logger;
        public IFreeSql<MesDB> db;
        public int Id {  get; set; }
        public PlcClient(PlcConfig _config, IServiceProvider _serviceProvider) { 
            this.plcConfig = _config;
            this.Id = _config.Id;
            serviceProvider = _serviceProvider;
            logger = LogHelper.PlcLogger;
            try
            {
                db = AppCache.GetServiceProvider().GetService<IFreeSql<MesDB>>();
               
            }
            catch (Exception)
            {

            }
            
        }
       
        public Plc plcSession;

        private List<CksPlcDataPosition> AllDataPosition = null;       //所有点位
        private List<CksPlcDataPosition> SubscriptionPosition = null;  //订阅点位
        private DateTime LastHeartTime = DateTime.Now;


        public class DataBlock
        {
            public DataBlock(int blockDb, int blockLenth, Task<byte[]> readTask, SieminDecoder decoder)
            {
                BlockDb = blockDb;
                BlockLenth = blockLenth;
                ReadTask = readTask;
                Decoder = decoder;
            }

            public int BlockDb {  get; set; }
            public int BlockLenth { get; set; }
            public Task<byte[]> ReadTask { get; set; }
            public SieminDecoder Decoder {  get; set; }
        }


        /// <summary>
        /// BlockDb 数据块 
        /// BlockLenth 大小
        /// Task<byte[]> 异步读取的结果
        /// </summary>
        private List<DataBlock> DataBlocks = new List<DataBlock>();   // 需要读取的数据块

        /// <summary>
        /// 启动客户端
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task StartAsync(CancellationToken cancellation)
        {
            await Task.Yield();

            plcSession = plcConfig.CpuType switch { 
                "1500" => new Plc(CpuType.S71500, plcConfig.PlcAddress, 0, 1),
                _ => new Plc(CpuType.S71200, plcConfig.PlcAddress, 0, 1)
            };
            plcSession.ReadTimeout = 2000;
            plcSession.WriteTimeout = 2000;
            BindCacheData();

            await Execute(cancellation);
           
        }

        private void BindCacheData()
        {
            //获取需要读取的数据块
            AllDataPosition = db.Queryable<CksPlcDataPosition>().Where(a => a.PlcId == plcConfig.Id && a.Module ==2   ).ToList();
            SubscriptionPosition = AllDataPosition.FindAll(a => a.EffectType == 1 || a.EffectType == 3 ||a.EffectType == 4);
            //提取订阅点的数据块批量读取
            DataBlocks.Clear();
            foreach (var item in SubscriptionPosition)
            {
                var findex = DataBlocks.FindIndex(a => a.BlockDb == item.BlockDb);
                if (findex < 0)
                {
                    //该DB块内的所有数据
                    var points = AllDataPosition.FindAll(a => a.BlockDb == item.BlockDb);
                    DataBlocks.Add(new(item.BlockDb, item.BlockLenth, null, new SieminDecoder(points)));
                }
            }
        }

        public async Task ConnectPlc(CancellationToken cancellation)
        {
            try
            {
                if (!plcSession.IsConnected)
                {
                    await plcSession.OpenAsync(cancellation);
                    if (plcSession.IsConnected)
                        logger.Information($"plc:{plcSession.IP} 连接成功");
                }
                IsConnected = plcSession.IsConnected;
            }
            catch (Exception ex)
            {

                //logger.Warning($"plc:{plcSession.IP}连接异常 {ex.Message}");
                IsConnected = false;
            }
        }

        /// <summary>
        /// 每xx毫秒读取一次
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task Execute(CancellationToken cancellation)
        {

            try
            {
                while (!cancellation.IsCancellationRequested)
                {
                    //检查当前PLC状态是否禁用 
                    //检查当前PLC的通信点位是否有变化，有变化需要重新加载
                    var model =db.Queryable<PlcConfig>().Where(a => a.Id == plcConfig.Id).First();
                    if(model == null || model.AgvEnable == false)
                    {
                        await Task.Delay(1000, cancellation);
                        continue;
                    }
                    bool pointIsChange = false;
                    var alls = db.Queryable<CksPlcDataPosition>().Where(a => a.PlcId == plcConfig.Id && a.Module == 2).ToList();
                    if(alls.Count == 0)
                    {
                        await Task.Delay(1000, cancellation);
                        continue;
                    }
                    
                    if (alls.Count != AllDataPosition.Count)
                        pointIsChange = true;
                    else if(alls.Count>0)
                    {
                        for (int i = 0; i < alls.Count; i++)
                        {
                            var id = alls[i].Id;
                            var a = alls[i];
                            var b = AllDataPosition.Find(x => x.Id == id);
                            if (b == null)
                                pointIsChange = true;
                            else if (b.BytePosition != a.BytePosition)
                                pointIsChange = true;
                            else if (b.BlockDb != a.BlockDb)
                                pointIsChange = true;
                            else if (!b.ParseType.Equals(a.ParseType))
                                pointIsChange = true;
                        }
                    }
                    else
                    {//没有点位数据
                        await Task.Delay(1000, cancellation);
                        continue;
                    }
                    if(pointIsChange)
                    {
                        BindCacheData();
                    }


                    await ConnectPlc(cancellation);
                    if(IsConnected)
                    {
                        DoHeart();
                        await ExecuteMain();
                    }
                 

                    
                    await Task.Delay(30, cancellation);
                }
                await Task.CompletedTask;
            }
            catch (Exception ex)
            {
                logger.Warning($"异常 {ex.Message}");
            }

            
        }

        int HeartSeed = 1;
        int PlcLastHeart = -1;
        int plcHeartValue = 0;
        bool IsConnected = false;
        bool LastConnected = false;
        public void DoHeart()
        {

            try
            {
                var ts = DateTime.Now - LastPlcHeart;
                if (ts.TotalSeconds < 5)
                {
                    IsOnline = true;
                
                }
                else
                {
                    IsOnline = false;
                   
                }


                ts = DateTime.Now - LastHeartTime;
                if (ts.TotalMilliseconds > 500)
                {
                    HeartSeed++;
                    if (HeartSeed > 1000)
                        HeartSeed = 1;
                    Int16 valueToWrite = (short)(HeartSeed % 2);
                    //查询心跳点位
                    var point = AllDataPosition.Find(a => a.EffectType == 2);
                    if (point != null && plcSession.IsConnected)
                    {
                        plcSession.Write(DataType.DataBlock, point.BlockDb, (int)point.BytePosition, valueToWrite);
                        LastHeartTime = DateTime.Now;
                        //读取PLC心跳值
                        var plcheart = AllDataPosition.Find(a => a.EffectType == 3);
                        if (plcheart != null)
                            plcHeartValue = (short)plcSession.Read(DataType.DataBlock, plcheart.BlockDb, (int)plcheart.BytePosition, VarType.Int, 1);
                    }

                    if (PlcLastHeart != plcHeartValue || IsConnected != LastConnected)
                    {
                        //logger.Information($"变更心跳值{plcHeartValue}");
                       // EventBus.PlcHeart(this, plcHeartValue);
                        LastPlcHeart = DateTime.Now;
                    }
                    LastConnected = IsConnected;
                }
            }
            catch (Exception ex)
            {
                logger.Warning($"写入MES心跳异常 {ex.Message}");
            }
        }


       
        

        /// <summary>
        /// 执行订阅方法
        /// </summary>
        public void ExecuteSubscriptionAction(DecodeValue value)
        {
            //var ActionBaseType = PlcActionDLLManage.GetActionBaseType();
            //// 获取方法
            //MethodInfo method = ActionBaseType.GetMethod("GetInstance");

            //var instance = method.Invoke(null, [value]) as ActionBase2;
            //instance.InitConfig(serviceProvider);
            //instance.Execute();
        }


        /// <summary>
        /// 读取订阅点位
        /// </summary>
        /// <returns></returns>
        public async Task ExecuteMain()
        {
            try
            {
                if (!plcSession.IsConnected)
                { return; }
                if (DataBlocks.Count == 0)
                { return; }



                //获取需要读取的数据块
                // siemenPlc.ReadBytesAsync(DataType.DataBlock, item.Key, 0, item.Value);
                List<Task<byte[]>> tasks = new List<Task<byte[]>>();

                for (int i = 0; i < DataBlocks.Count; i++)
                {
                    var block = DataBlocks[i];
                   
                    var task = plcSession.ReadBytesAsync(DataType.DataBlock, block.BlockDb, 0, block.BlockLenth);
                    block.ReadTask = task;
                    tasks.Add(task);
                }
                await Task.WhenAll(tasks);
                //
                foreach (var block in DataBlocks)
                {
                    var blockBytes = block.ReadTask.Result; //异步任务结果
                                                            //解析所有点位数据
                    var DecodeValues = block.Decoder.Decode(blockBytes);
                    var subscriptionCodes = DecodeValues.FindAll(a=>a.Position.EffectType == 1 || a.Position.EffectType == 4 );
                    foreach (var val in subscriptionCodes)
                    {
                        val.AllValues = DecodeValues;
                        if (val.IsChange || val.Position.EffectType == 4)
                        {
                          
                            val.plcSession = plcSession;
                            val.AllPositions = AllDataPosition;

                            val.LinkPositions = AllDataPosition.FindAll(a => a.LinkActionId == val.Position.ActionId);
                           ProccessPointValue(val);
                          //  ExecuteSubscriptionAction(val);
                            val.OldValue = val.Value; //处理完成后将值赋给旧值变量

                        }
                    }

                }
            }
            catch (Exception ex)
            {
                logger.Warning($"操作plc - {plcConfig.Id} 异常  {ex.Message}");
                EventBus.AddLog($"操作plc - {plcConfig.Id} 异常  {ex.Message}");
            }

        }




        private DateTime LastPlcHeart = AppCache.MinTime;
        /// <summary>
        /// 当前PLC是否在线
        /// </summary>
        public bool IsOnline { get; set; }


        /// <summary>
        /// 处理订阅点位变化信息
        /// </summary>
        /// <param name="value"></param>
        public void ProccessPointValue(DecodeValue value)
        {
            //如果订阅点的数据是1：执行操作， 
            //否则执行复位操作

            BasePlcAction act = BasePlcAction.GetInstance(value);
            act?.Execute();
       
        }

        public int? ReadInt16(int db, int startByteAdr)
        {
            try
            {
                return (int)plcSession.Read(DataType.DataBlock,db,startByteAdr,VarType.Int,1);
            }
            catch (Exception)
            {
                return null;
            }
        }



        /// <summary>
        /// 增加虚拟信号，触发虚拟事件
        /// </summary>
        public void GetLinkData()
        {
            
        }
        /// <summary>
        /// 取料反馈  state: 1取料到达 2取料离开
        /// </summary>
        /// <param name="agvtask"></param>
        /// <param name="state"> 1取料到达 2取料离开</param>
        internal void NotifyMaterialRetrievalResult(agv_convey_tasks agvtask, AgvArriveStatus state)
        {
            try
            {
                if (!this.IsConnected)
                {
                    return;
                }
                BasePlcAction act = new NotifyPlcAgvStatusAction(null);

                act.Client = this;
                act.AllDataPosition = AllDataPosition;
                act.FetchMaterialArrive(agvtask, state);
            }
            catch (Exception ex)
            {

            }
            
            
        }
        /// <summary>
        /// 送料反馈
        /// </summary>
        /// <param name="agvtask"></param>
        /// <param name="state">1送料到达 2送料离开</param>
        internal void NotifyMaterialDeliveryResult(agv_convey_tasks agvtask,int state)
        {
            try
            {
                if (!this.IsConnected)
                {
                    return;
                }
                BasePlcAction act = new NotifyPlcAgvStatusAction(null);
                act.Client = this;
                act.AllDataPosition = AllDataPosition;
                act.NotifyMaterialDeliveryResult(agvtask, (AgvArriveStatus)state);
            }
            catch (Exception ex)
            {

            }

        }

        public void ResetPlcGenerTask(agv_convey_tasks agv_task)
        {
            try
            {
                if (!this.IsConnected)
                {
                    return;
                }
                var plcid = 0;
                if (agv_task.TaskType == 1)
                    plcid = agv_task.EndPlcId;
                else if (agv_task.TaskType == 2)
                    plcid = agv_task.StartPlcId;
               



            }
            catch (Exception)
            {

            }
        }
        /// <summary>
        /// 潜伏式 AGV 
        /// </summary>
        /// <param name="item"></param>
        /// <exception cref="NotImplementedException"></exception>
        internal void ResetPlcAgvTaskStatus1(agv_convey_tasks agvtask , agv_path_point callerPoint)
        {
            BasePlcAction act = new NotifyPlcAgvStatusAction(null);
            act.Client = this;
            act.AllDataPosition = AllDataPosition;
            act.ResetPlcAgvTaskStatus1(agvtask, callerPoint);
        }
    }
}
